package com.sh.rpc.spi;


import cn.hutool.core.io.resource.ResourceUtil;
import lombok.extern.slf4j.Slf4j;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.URL;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * spi  加载器
 * 支持键值对映射
 * 一但有地方调用这里的 static load 就会触发类加载哦
 * @author sh
 */
@Slf4j
public class SpiLoader {

    /**
     * 存储已经加载的类   key:类接口名                      value: 类接口名 -> 实现类
     *             com.sh.serializer.Serializer                json      xx@15625
     */
    private static Map<String, Map<String, Class<?>>> loaderMap = new ConcurrentHashMap<>();

    /**
     * 对象实例缓存
     * key:类路径  value:实例
     */
    private static Map<String, Object> instanceCache = new ConcurrentHashMap<>();

    /**
     * 系统spi目录
     */
    private static final String RPC_SYSTEM_SPI_DIR = "META-INF/rpc/system/";

    /**
     * 用户自定义spi目录
     */
    private static final String RPC_CUSTOM_SPI_DIR = "META-INF/rpc/custom/";

    /**
     * 扫描路径
     */
    private static final String[] SCAN_DIRS = new String[]{RPC_SYSTEM_SPI_DIR, RPC_CUSTOM_SPI_DIR};

    /**
     * 动态加载的类列表，被加载的类列表
     * aslist: 内容可以改变，大小不能改变
     */
    private static final List<Class<?>> LOAD_CLASS_LIST = Arrays.asList(SpiLoader.class);


    /**
     * 加载所有类
     */
    public static void loadAll() {
        log.info("开始加载spi");
        for (Class<?> aClass : LOAD_CLASS_LIST) {
            load(aClass);
        }
    }

    /**
     * 加载某个类
     * @param loadClass
     */
    public static Map<String, Class<?>> load(Class<?> loadClass) {
//        log.info("开始加载类:{}", loadClass);
        // 扫描自定义的路径，  规定：用户自定义的 > 系统spi目录
        Map<String, Class<?>> keyClassMap = new HashMap<>();
        for (String scanDir : SCAN_DIRS) {
            // getResource方法用于获取资源文件
            List<URL> resources = ResourceUtil.getResources(scanDir + loadClass.getName());
            for (URL resource : resources) {
                try {
                    // URL.openStream()方法用于打开一个输入流，以便从指定的 URL 读取数据。
                    InputStreamReader inputStreamReader = new InputStreamReader(resource.openStream());
                    BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
                    String line;
                    while ((line = bufferedReader.readLine()) != null) {
                        // xxx = xxx
                        String[] strArray = line.split("=");
                        if (strArray.length > 1) {
                            String key = strArray[0];
                            String className = strArray[1];
                            keyClassMap.put(key, Class.forName(className));
//                            System.out.println("添加了" + key + "=" + className);
                        }
                    }
                } catch (Exception e) {
                    log.error("spi加载异常:{}", e);
                    e.printStackTrace();
                }
            }
        }
        loaderMap.put(loadClass.getName(), keyClassMap);
        return keyClassMap;
    }


    /**
     * 获取某个接口的实例
     */
    public static <T> T getInstance(Class<T> tClass, String key) {
        // com.sh.rpc.registry.Registry
        String tClassName = tClass.getName();  // 获取接口名
        // {jdk=class com.sh.serializer.JdkSerializer, hessian=class com.sh.serializer.HessianSerializer, json=class com.sh.serializer.JsonSerializer}
        Map<String, Class<?>> keyClassMap = loaderMap.get(tClassName);  // 获取接口对应的实现类
        if (keyClassMap == null) {
            throw new RuntimeException(String.format("SpiLoader 没有找到 %s 的配置信息", tClassName));
        }
        if (!keyClassMap.containsKey(key)) {
            throw new RuntimeException(String.format("%s 找不到 %s 的配置信息", tClassName, key));
        }
        Class<?> implClass = keyClassMap.get(key);  // 获取键   hessian
        String implClassName = implClass.getName();  // 获取实现类的全限定名  com.sh.serializer.JsonSerializer
        // 如果实例缓存中存在，则直接返回  单例
        if (!instanceCache.containsKey(implClassName)) {
            try {
                // HessianSerializer  ：  实例
                instanceCache.put(implClassName, implClass.newInstance());
            } catch (Exception e) {
                String errorMsg = "SpiLoader 创建 %s 的实例失败" + implClassName;
                throw new RuntimeException(errorMsg, e);
            }
        }
        //   com.sh.serializer.JsonSerializer@3439f68d  实例
        return (T) instanceCache.get(implClassName);
    }

}
